#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QIODevice>
#include <QTextStream>
#include <unistd.h>
#include <utime.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include "zip_programfile.h"

#define WRITEBUFFERSIZE (16384)
#define MAXFILENAME (256)
#ifdef __APPLE__
// In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for specific 64 bit functions
#define FOPEN_FUNC(filename, mode) fopen(filename, mode)
#define FTELLO_FUNC(stream) ftello(stream)
#define FSEEKO_FUNC(stream, offset, origin) fseeko(stream, offset, origin)
#else
#define FOPEN_FUNC(filename, mode) fopen64(filename, mode)
#define FTELLO_FUNC(stream) ftello64(stream)
#define FSEEKO_FUNC(stream, offset, origin) fseeko64(stream, offset, origin)
#endif



ZipProgramFile::ZipProgramFile(QObject *parent) : QObject(parent)
{

}

void ZipProgramFile::zipProgramFile(const QString &zipName,
                    const QString &folderPath)
{
    QStringList folderNames;
    QList<ZipFilePath> fileNames;
    QDir dirRoot(folderPath);
    // 获取所有有效目录
    folderNames.append("");
    folderNames.append(dirRoot.entryList(QDir::Dirs | QDir::NoDot | QDir::NoDotDot));

    for (auto &path : folderNames) {
        QDir tmpDir(folderPath + path);
        for (const QString &fileName : tmpDir.entryList(QDir::Files)) {
            ZipFilePath _tmpFilePath;
            _tmpFilePath.rootPath = folderPath + path + "/" + fileName;
            _tmpFilePath.savePath = path.isEmpty() ? fileName : path + "/" + fileName;
            fileNames.append(_tmpFilePath);
        }
    }
    this->zipFiles(zipName, fileNames);
}

int ZipProgramFile::isLargeFile(std::string filename)
{
    int largeFile = 0;
    ZPOS64_T pos = 0;
    FILE* pFile = FOPEN_FUNC(filename.c_str(), "rb");

    if(pFile != NULL)
    {
        FSEEKO_FUNC(pFile, 0, SEEK_END);
        pos = FTELLO_FUNC(pFile);

        if(pos >= 0xffffffff)
            largeFile = 1;

        fclose(pFile);
    }

    return largeFile;
}


uLong ZipProgramFile::filetime(const char* f, tm_zip *tmzip, uLong *dt)
{
  int ret=0;
  struct stat s;        /* results of stat() */
  struct tm* filedate;
  time_t tm_t=0;

  if (strcmp(f,"-")!=0) {
    char name[MAXFILENAME+1];
    int len = strlen(f);
    if (len > MAXFILENAME)
      len = MAXFILENAME;

    strncpy(name, f,MAXFILENAME-1);
    /* strncpy doesnt append the trailing NULL, of the string is too long. */
    name[ MAXFILENAME ] = '\0';

    if (name[len - 1] == '/')
      name[len - 1] = '\0';
    /* not all systems allow stat'ing a file with / appended */
    if (stat(name,&s)==0) {
      tm_t = s.st_mtime;
      ret = 1;
    }
  }
  filedate = localtime(&tm_t);

  tmzip->tm_sec  = filedate->tm_sec;
  tmzip->tm_min  = filedate->tm_min;
  tmzip->tm_hour = filedate->tm_hour;
  tmzip->tm_mday = filedate->tm_mday;
  tmzip->tm_mon  = filedate->tm_mon ;
  tmzip->tm_year = filedate->tm_year;

  return ret;
}


int ZipProgramFile::zipFiles(const QString &zipFileName,
                             const QList<ZipFilePath> &files,
                             bool bAppendFile,
                             int nCompressLevel)
{
    zipFile zf;
    int err, errclose;
    zf = zipOpen64(zipFileName.toStdString().c_str(), (bAppendFile) ? 2 : 0);
    if (zf == NULL) {
        printf("error opening %s\n", zipFileName.toUtf8().data());
        err = ZIP_ERRNO;
    } else {
        printf("creating %s\n", zipFileName.toUtf8().data());
        err = ZIP_OK;
    }

    qint64 i = 0;
    for (; i < files.length() && (err == ZIP_OK); i++) {
        QString filenameinzip = files[i].rootPath;
        QString savefilenameinzip = files[i].savePath;
        zip_fileinfo zi;
        unsigned long crcFile = 0;
        int zip64 = 0;

        zi.tmz_date.tm_sec = zi.tmz_date.tm_min = zi.tmz_date.tm_hour =
                zi.tmz_date.tm_mday = zi.tmz_date.tm_mon = zi.tmz_date.tm_year = 0;
        zi.dosDate = 0;
        zi.internal_fa = 0;
        zi.external_fa = 0;
        filetime(filenameinzip.toUtf8().data(), &zi.tmz_date, &zi.dosDate);

        zip64 = isLargeFile(filenameinzip.toUtf8().data());
        QByteArray fileNameBuf = savefilenameinzip.toUtf8();
        char utf8Buf[256]{0};
        memcpy(utf8Buf, fileNameBuf.data(), fileNameBuf.length() < 256 ? fileNameBuf.length() : 255);
        err = zipOpenNewFileInZip3_64(zf, utf8Buf, &zi,
                                      NULL, 0, NULL, 0, NULL /* comment*/,
                                      (nCompressLevel != 0) ? Z_DEFLATED : 0,
                                      nCompressLevel, 0,
                                      /* -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, */
                                      -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
                                      NULL, crcFile, zip64);

        if (err != ZIP_OK) {
            printf("error in opening %s in zipfile\n", filenameinzip.toUtf8().data());
        } else {
            QString fileContent = this->readFile(filenameinzip);
            QByteArray buf = fileContent.toUtf8();
            err = zipWriteInFileInZip(zf, buf.data(), buf.length());
        }

        if (err < 0) {
            err = ZIP_ERRNO;
        } else {
            err = zipCloseFileInZip(zf);
            if (err != ZIP_OK)
                printf("error in closing %s in the zipfile\n",
                       filenameinzip.toUtf8().data());
        }
    }

    errclose = zipClose(zf, NULL);
    if (errclose != ZIP_OK)
        printf("error in closing %s\n", zipFileName.toUtf8().data());

    return err;
}

QString ZipProgramFile::readFile(const QString &filePath)
{
    QFile file(filePath);
    if (!file.exists()) {
        return "";
    }
    if (file.open(QIODevice::ReadOnly)) {
        QTextStream in(&file);
        in.setCodec("utf-8");
        QString tmp = in.readAll();
        file.close();
        return tmp;
    }
    return "";
}
